/*  This file is part of LibGChart.
 *
 * AUTHORS
 *       Sascha Dewald        <sascha.dewald@googlemail.com>
 *
 * Copyright (C) 2009 Sascha Dewald
 *
 *  
 *  
 *  LibGChart is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Foobar is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with LibGChart.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;

using Cairo;

namespace LibGChart
{
	/**
	 * AxisOrientation enum
	 */
	internal enum AxisOrientation
	{
		Left,
		Bottom
	}
	
	/**
	 * BufferMajorTicks
	 */
	internal struct BufferMajorTicks
	{
		internal double tickPosParallel;
		internal double labelPosParallel;
		
		internal string label;
	}
	
	/**
	 * Global Function Pointer, for OnChanged event
	 */
	internal delegate void DelegateOnChanged();
	
	/**
	 * GAxis class
	 */ 
	public class GAxis
	{
		private AxisOrientation orientation;
		
		private GCaption title;
		private GCaption labels; // one for all
		
		//public double offsetAxisStart;
		//public double offsetAxisStop;
		//public double offsetAxisBorder;
		//public double axisParallelStop;
		
		private double axisOffset;
		private double offsetChartUprightStart;
		private double offsetChartUprightStop;
		private double offsetChartParallelStart;
		private double offsetChartParallelStop;

		//internal GAxis uprightAxis; // needed for offset calculation...
		
		private double maxLabelWidth = 0;
		
		// min max values
		private double valueMin, valueMax; // will be set from the user
		//private double visibleMin, visibleMax; // scroll & zoom values
		private double realValueMin, realValueMax; // rounded values, for the default zoom level
		
		// ticks params
		private int ticksMax;
		private int ticksCount;
		private double ticksSpace;
		private double ticksInterval;
		
		// ticks parameter
		private int ticksMajorLength = 12;
		private int ticksMinorLength = 6;
		
		/*
		 * coords
		 */
		//private double axisStartParallel;
		private double axisStartUpright;
		
		private double ticksStopUpright; // coord ticks stop upright
		
		private double axisStartX;
		private double axisStartY;
		private double axisStopX;
		private double axisStopY;

		private double axisLength;
		
		// Parent Chart
		//private GChart chart;
		
		// Color Vars
		private Color color;
		private Color colorMajorTicks;
		private Color colorMinorTicks;
		private Color colorGrid;
		
		// Ticks, Grids and Labels Buffer
		private BufferMajorTicks[] bufferMajorTicks;
		
		// events
		internal event DelegateOnChanged onChanged;
		internal event DelegateOnChanged onOffsetChanged;
		internal event DelegateOnChanged onTicksChanged;
		internal event DelegateOnChanged onNeedRedraw;
		
		/**
		 * constructor
		 */
		internal GAxis (GChart parent, AxisOrientation orientation): base ()
		{
			//Title = "Axis 1";
			//chart = parent;
			
			Console.WriteLine("Construct Axis");
			
			color = new Color(0.0, 0.3, 0.8);
			//colorLables = new Color(0.0, 0.0, 0.0);
			colorMinorTicks = new Color(0.6, 0.6, 0.6);
			colorMajorTicks = new Color(0.6, 0.6, 0.6);
			colorGrid = new Color(0.5, 0.5, 0.5);
			
			title = new GCaption();
			labels = new GCaption();
			
			title.FontSpace = 3;
			title.FontSize = 12;

			labels.Text = "-9876543210.0123456789";
			labels.FontSpace = 3;
			labels.FontSize = 12;
			
			this.Orientation = orientation;
			
			//labels.OnWidthChanged += update_upright_coords_start;
			labels.OnFontSizeChanged += update_upright_coords_start;
			labels.OnFontSpaceChanged += update_parallel_coords;
			labels.OnFontSizeChanged += update_parallel_coords;
			
			labels.OnFontSizeChanged += redraw;
			labels.OnFontSpaceChanged += redraw;
			
			title.OnFontSpaceChanged += update_upright_coords_start;
			title.OnHeightChanged += update_upright_coords_start;
			title.OnWidthChanged += update_title_pos_parallel;
			
			title.OnFontSpaceChanged += redraw;
			title.OnTextChanged += redraw;
			title.OnWidthChanged += redraw;
		}
		
		/**
		 * Title of the Axis
		 */
		public GCaption Title {
			get { return title; }
		}
		
		/**
		 * Labels property
		 */
		public GCaption Labels {
			get { return labels; }
		}
		
		/**
		 * Property Orientation
		 */
		private AxisOrientation Orientation {
			get { return orientation; }
			set {
				orientation = value;
				
				if (title != null)
				{
					if (orientation == AxisOrientation.Left)
					{
						title.rotation = Math.PI * 1.5;
						labels.rotation = 0;
					}
					else
					{
						title.rotation = 0;
						labels.rotation = Math.PI * 1.5;
					}
				}
			}
		}
		
		/**
		 * Property ValueMin
		 */
		public double ValueMin
		{
			get {
				return valueMin;
			}
			
			set {
				if (valueMin != value)
				{
					valueMin = value;
					
					update_tick_division(true);
				}
			}
		}
		
		/**
		 * Property ValueMax
		 */
		public double ValueMax
		{
			get {
				return valueMax;
			}
			
			set {
				if (ValueMax != value)
				{
					valueMax = value;
					
					update_tick_division(true);
				}
			}
		}

		/**
		 * AxisOffset
		 */
		internal double AxisOffset 
		{
			get {
				return axisOffset;
			}
			set {
				if (axisOffset != value)
				{
					axisOffset = value;
					
					if (onOffsetChanged != null)
						onOffsetChanged();
				}
			}
		}
		
		/**
		 * AxisLength
		 */
		private double AxisLength
		{
			get { return axisLength; }
			set { 
				if (axisLength != value)
				{
					axisLength = value;
				}
			}
		}
		
		/**
		 * ticksInterval
		 */
		private double TicksInterval
		{
			get { return ticksInterval; }
			set { 
				if (ticksInterval != value)
				{
					ticksInterval = value;
				}
			}
		}

		/**
		 * ticksCount
		 */
		private int TicksCount
		{
			get { return ticksCount; }
			set {
				if (ticksCount != value)
				{
					ticksCount = value;
				}
			}
		}
		
		/**
		 * ticksSpace
		 */
		private double TicksSpace
		{
			get { return ticksSpace; }
			set {
				if (ticksSpace != value)
				{
					ticksSpace = value;
				}
			}
		}
		
		/**
		 * OffsetChartUprightStart
		 */
		internal double OffsetChartUprightStart
		{
			get { return offsetChartUprightStart; }
			set { 
				if (offsetChartUprightStart != value)
				{
					offsetChartUprightStart = value;
					
					update_upright_coords_start();
				}
			}
		}
		
		/**
		 * OffsetChartUprightStop
		 */
		internal double OffsetChartUprightStop
		{
			get { return offsetChartUprightStop; }
			set { 
				if (offsetChartUprightStop != value)
				{
					offsetChartUprightStop = value;
				}
			}
		}
		
		/**
		 * OffsetChartParallelStart
		 */
		internal double OffsetChartParallelStart
		{
			get { return offsetChartParallelStart; }
			set { 
				if (offsetChartParallelStart != value)
				{
					offsetChartParallelStart = value;
					
					update_parallel_coords();
				}
			}
		}
		
		/**
		 * OffsetChartParallelStop
		 */
		internal double OffsetChartParallelStop
		{
			get { return offsetChartParallelStop; }
			set { 
				if (offsetChartParallelStop != value)
				{
					offsetChartParallelStop = value;
					
					update_parallel_coords();
				}
			}
		}
		
		/**
		 * MaxLabelWidth
		 */
		private double MaxLabelWidth
		{
			get { return maxLabelWidth; }
			set {
				if (maxLabelWidth != value)
				{
					maxLabelWidth = value;
					
					update_upright_coords_start();
				}
			}
		}
		
		/**
		 * internal function 
		 * calculate the Axis Coordination
		 */
		internal double calc_coord_value(double value)
		{
			double tmpCoord = ((value - realValueMin) * axisLength) / (realValueMax - realValueMin);
			//Console.WriteLine(offsetChartParallelStart + tmpCoord);
			if (orientation == AxisOrientation.Left) 
				return axisStartY - tmpCoord;
			else //if (orientation == AxisOrientation.Bottom)
				return axisStartX + tmpCoord;
		}
		
		/**
		 * set the color of the Axis
		 */
		public void set_color(double r, double g, double b)
		{
			color = new Color(r, g, b);
		}
		
		/**
		 * set the color of the major ticks
		 */
		public void set_color_major_ticks(double r, double g, double b)
		{
			colorMajorTicks = new Color(r, g, b);
		}
		
		/**
		 * set the color of the minor ticks
		 */
		public void set_color_minor_ticks(double r, double g, double b)
		{
			colorMinorTicks = new Color(r, g, b);
		}
		
		/**
		 * set the color of the grid
		 */
		public void set_color_grid(double r, double g, double b)
		{
			colorGrid = new Color(r, g, b);
		}
		
		/**
		 * redraw
		 */
		private void redraw()
		{
			if (onNeedRedraw != null)
				onNeedRedraw();
		}
		
		/**
		 * update_upright_coords_start
		 */
		private void update_upright_coords_start()
		{
			update_offset();
			update_title_pos_upright(); // update title and label pos
			update_axis_pos_upright();  // update axis pos and ticks pos
		}
		
		/**
		 * update_parallel_coords
		 */
		private void update_parallel_coords()
		{
			axisLength = offsetChartParallelStop - offsetChartParallelStart;
			
			update_title_pos_parallel();
			update_axis_pos_parallel();
			
			update_tick_division(false);
		}
		
		/**
		 * update_tick_division
		 */
		private void update_tick_division(bool forceRecalc)
		{
			check_ticks_need_recalc(forceRecalc);
			calc_ticks_coords_parallel();
		}
		
		/**
		 * protected function, calculate the static part of the axis
		 * will be called, if something changed
		 */
		protected void update_offset()
		{
			AxisOffset = 1 + ticksMajorLength + title.Height + 2 * title.FontSpace + 2 * labels.FontSpace + maxLabelWidth;
		}
		
		/**
		 * check_ticks_need_recalc
		 */
		private void check_ticks_need_recalc(bool forceRecalc)
		{
			int ticksCountOld = ticksCount;
			double ticksSpaceOld = ticksSpace;
			double ticksIntervalOld = ticksInterval;
			
			ticksMax = (int) ((axisLength) / (labels.Height + labels.FontSpace));
			double countLabelsSpace = ticksSpace / (labels.Height + labels.FontSpace);
			update_tick_space();
			
			
			
			if (
			    (ticksMax < ticksCount)
			    ||
			    (countLabelsSpace > 4)
				||
			    (ticksSpace < (labels.Height + (2 * labels.FontSpace)))
			    || 
			    (forceRecalc)
			   )
			{
				calc_tick_division();
			}
			
			if (
			    (ticksCount != ticksCountOld)
			    ||
			    (ticksSpace != ticksSpaceOld)
			    ||
			    (ticksInterval != ticksCountOld)
			   )
			{
				if (onTicksChanged != null)
				{
					onTicksChanged();
				}
			}
		}
		
		/**
		 * calc_tick_division
		 */
		private int calc_tick_division()
		{
			int ticksCountAlt = ticksCount;
			double ticksIntervalMax;
			int tmpTicksMax;
				
			//ticksMax 	= (int) (axisLength / (labels.Height + labels.FontSpace));
			tmpTicksMax 	= (int) (ticksMax * 0.66);
			ticksIntervalMax = Math.Abs(valueMax - valueMin) / (tmpTicksMax - 1); // -1, so we have one tick more space
			
			double wertMass = Math.Pow(10, Math.Ceiling(Math.Log10(ticksIntervalMax)));
			
			if ((wertMass / 8.0) >= ticksIntervalMax)
			{
				ticksInterval = (wertMass / 8.0); //0,125
				// MajorTicks every 8th tick
			}
			else if ((wertMass / 5.0) >= ticksIntervalMax)
			{
				ticksInterval = (wertMass / 5.0); //0,2
				// MajorTicks every 5th tickinternal
			}
			else if ((wertMass / 4.0) >= ticksIntervalMax)
			{
				ticksInterval = (wertMass / 4.0); //0,25
				// MajorTicks every 4th tick
			}
			else if ((wertMass / 2.0) >= ticksIntervalMax)
			{
				ticksInterval = (wertMass / 2.0); // 0,5
				// MajorTicks every 5th tick
			}
			else if ((wertMass / 1.0) >= ticksIntervalMax)
			{
				ticksInterval = (wertMass / 1.0);
				// MajorTicks every 5th tick
			}
			
			double tmpModulo = valueMin % ticksInterval;
			
			// Set the real Max and Min Values, and the Count of ticks
			//Console.WriteLine("MODULO: " + (valueMin % ticksInterval));
			if (tmpModulo >= 0)
				realValueMin = valueMin - (tmpModulo);
			else
				realValueMin = valueMin - (ticksInterval + tmpModulo);
			
			ticksCount = (int) Math.Ceiling((Math.Abs(valueMax - realValueMin) / (ticksInterval)));
			realValueMax = realValueMin + (ticksCount * ticksInterval);
			
			update_tick_space();
			
			//calc_labels_extents();
			
			if (ticksCountAlt != ticksCount)
			{
				if (ticksCount > 0)
				{
					bufferMajorTicks = new BufferMajorTicks[ticksCount+1];
					
				}
			}
			
			return ticksCount;
		}
		
		private void update_tick_space()
		{
			ticksSpace = axisLength / (double) ticksCount;
		}
		
		/**
		 * calc_ticks_coords_parallel
		 */
		private void calc_ticks_coords_parallel()
		{
			double tmpOffset, tmp;
			double widthMax = 0;
			double tmpWidth = 0;
			
			Cairo.Context cr = OffScreenContextHelper.OffscreenContext;
			
			if (ticksCount <= 0)
			{
				Console.WriteLine("ERROR tickscount");
				return;
			}
			
			//double labelPosParallel = offsetChartParallelStart - labels.HeightHalf;
			//labels.PosParallel = Math.Abs(labelPosParallel);
			
			cr.SetFontSize(labels.FontSize);
		
			for (int i =  0; i <= ticksCount; i++)
			{
				tmpOffset = i * ticksSpace;
				tmp = Math.Abs(offsetChartParallelStart + tmpOffset); // Math.Abs because Y coords are negativ
				
				bufferMajorTicks[i].tickPosParallel = tmp;
				bufferMajorTicks[i].labelPosParallel = tmp + labels.HeightHalf;
			
				bufferMajorTicks[i].label = (realValueMin + (i * ticksInterval)).ToString();
			
				tmpWidth = cr.TextExtents(bufferMajorTicks[i].label).Width;
				widthMax = Math.Max(widthMax, tmpWidth);
			}
			
			MaxLabelWidth = widthMax;
		}
		
		/**
		 * update_title_pos_parallel
		 */
		private void update_title_pos_parallel()
		{
			double titlePosParallel = offsetChartParallelStart + (axisLength / 2.0) - title.WidthHalf;
			
			switch (orientation)
			{
				case AxisOrientation.Left:
					title.PosUpright = Math.Abs(titlePosParallel);
					break;
			
				case AxisOrientation.Bottom:
					title.PosParallel = titlePosParallel;
					break;
			}
		}
		
		
		/**
		 * update_title_pos_upright
		 */
		private void update_title_pos_upright()
		{
			double titlePosUpright = title.FontSpace + offsetChartUprightStart;
			
			switch (orientation)
			{
				case AxisOrientation.Left:
					title.PosParallel = titlePosUpright + (-1 * title.YBearing);
					break;
			
				case AxisOrientation.Bottom:
					title.PosUpright = Math.Abs(titlePosUpright);
					break;
			}
			
			double labelPosUpright = offsetChartUprightStart + 2 * title.FontSpace + title.Height + labels.FontSpace;
			labels.PosUpright = Math.Abs(labelPosUpright); // Math.Abs because Y coords are negative
		}
		
		
		/**
		 * internal function "calc_axis_pos"
		 */
		private void update_axis_pos_parallel()
		{
			switch (orientation)
			{
				case AxisOrientation.Left:
					//axisStartParallel = Math.Abs(offsetChartParallelStart);
					axisStartY	= Math.Abs(offsetChartParallelStart);
					axisStopY	= Math.Abs(offsetChartParallelStop);
					break;
			
				case AxisOrientation.Bottom:
					//axisStartParallel = offsetChartParallelStart;
					axisStartX	= offsetChartParallelStart;
					axisStopX	= offsetChartParallelStop;
					break;
			}
		}
		

		/**
		 * update_axis_pos_upright
		 */
		private void update_axis_pos_upright()
		{
			switch (orientation)
			{
				case AxisOrientation.Left:
					axisStartUpright = AxisOffset + offsetChartUprightStart;
					axisStartX = axisStopX = AxisOffset + offsetChartUprightStart; 
					ticksStopUpright = axisStartUpright - ticksMinorLength;
					break;
			
				case AxisOrientation.Bottom:
					axisStartUpright = Math.Abs(AxisOffset + offsetChartUprightStart);
					axisStartY = axisStopY = Math.Abs(AxisOffset + offsetChartUprightStart);
					ticksStopUpright = axisStartUpright + ticksMinorLength;
					break;
			}
		}
		
		/**
		 * internal function "draw" - draw the Axis, the Ticks and the AxisTitle
		 */
		internal void draw(Cairo.Context cr)
		{
			cr.Save();
			drawAxis(cr);
			drawTicks(cr);
			title.draw(cr);
			cr.Restore();
		}
		
		/**
		 * function drawAxis - draws the Axis
		 */
		private void drawAxis(Cairo.Context cr)
		{
			// Save the State of the Graphics Context
			//cr.Save();
			
			cr.MoveTo(axisStartX, axisStartY);
			cr.LineTo(axisStopX, axisStopY);
			cr.Color = color;
			cr.Stroke();
			
			// Restore the State of the Graphics Context
			//cr.Restore();
		}
		
		/**
		 * function "drawTicks"
		 */
		private void drawTicks(Cairo.Context cr)
		{
			double[] tmpDashGrid = new double[]{3.0, 3.0};
			
			// Save the State of the Graphics Context
			//cr.Save();
			switch(orientation)
			{
				case AxisOrientation.Left:
					// Loop through MinorTicks
					cr.Color = colorMinorTicks;
					for (int i =  0; i <= ticksCount; i++)
					{
						drawLine(cr,
					             axisStartUpright, bufferMajorTicks[i].tickPosParallel,
					             ticksStopUpright, bufferMajorTicks[i].tickPosParallel,
					             2.0,
					             colorMajorTicks);
					
						// Draw Grid
						drawLineDash(cr,
					             axisStartUpright, bufferMajorTicks[i].tickPosParallel,
					             offsetChartUprightStop, bufferMajorTicks[i].tickPosParallel,
					             1.0,
					             colorGrid,
					             tmpDashGrid);
					
						// Draw Text
						drawText(cr, 
								labels.PosUpright, 
								bufferMajorTicks[i].labelPosParallel,
								labels.rotation,
								labels.FontSize,
								labels.Color,
								bufferMajorTicks[i].label);
					}
					break;
				
				case AxisOrientation.Bottom:
					// Loop through MinorTicks
					cr.Color = colorMinorTicks;
					for (int i =  0; i <= ticksCount; i++)
					{
						drawLine(cr,
					             bufferMajorTicks[i].tickPosParallel, axisStartUpright,
					             bufferMajorTicks[i].tickPosParallel, ticksStopUpright,
					             2.0,
					             colorMajorTicks);

						// Draw Grid
						drawLineDash(cr,
					             bufferMajorTicks[i].tickPosParallel, axisStartUpright,
					             bufferMajorTicks[i].tickPosParallel, offsetChartUprightStop,
					             1.0,
					             colorGrid,
					             tmpDashGrid);
					
						// Draw Text
						drawText(cr, 
								bufferMajorTicks[i].labelPosParallel, 
								labels.PosUpright,
								labels.rotation,
								labels.FontSize,
								labels.Color,
								bufferMajorTicks[i].label);
					}
					break;
			}
			// Restore the State of the Graphics Context
			//cr.Restore();
		}
		
		
		/**
		 * drawLine
		 */
		private void drawLine(Context cr, double startX, double startY, double stopX, double stopY, double lineWidth, Color color)
		{
			cr.Color = color;
			cr.LineWidth = lineWidth;
			cr.MoveTo(startX, startY);
			cr.LineTo(stopX, stopY);
			cr.Stroke();
		}
		
		/**
		 * drawLineDash
		 */
		private void drawLineDash(Context cr, double startX, double startY, double stopX, double stopY, double lineWidth, Color color, double[] dash)
		{
			cr.Color = color;
			cr.LineWidth = lineWidth;
			cr.SetDash(dash, 0);
			cr.MoveTo(startX, startY);
			cr.LineTo(stopX, stopY);
			cr.Stroke();
		}
		
		/**
		 * drawText
		 */
		private void drawText(Context cr, double x, double y, double rotation, int fontSize, Color color, string text)
		{
			cr.Save();
			cr.Color = color;
			cr.SetFontSize(fontSize);
			cr.MoveTo(x, y);
			cr.Rotate(rotation);
			cr.ShowText(text);
			cr.Restore();
		}
	}
}